#include <EIC.h>
#include <vcl.h>
#pragma hdrstop

#include "CTable.h"


#pragma package(smart_init)

#define MODULE_TAG 1


//**************//
// CFilterStack //
//**************////////////////////////////////////////////////////////////////
//                                                                            //
//    Cette classe permet de stocker, sous forme d'une pile, la description   //
// d'un filtre d'une table (Valeurs des proppriétés "Filter" et "Filtered").  //
//                                                                            //
// Note : La gestion d'un ID de filtre permet de ne dépiler que des filtres   //
//        qui ont bien été empilés (cela permet d'éviter les effets de bord   //
//        dans les gestionnaires d'exception).                                //
////////////////////////////////////////////////////////////////////////////////
class PACKAGE_CTRLDB CFilterStack
{
 private:
  static FilterID cm_fidDone; // Dernier ID d'un élément utilisé pour une pile de filtres

  const AnsiString m_strFilter; // Le texte du filtre
  bool             m_bFiltered; // Détermine si le filtre est actif
  FilterID         m_fid;       // ID du filtre
  CFilterStack*    m_pfdNext;   // Description suivante d'un filtre

 public:
  __property const AnsiString Filter   = { read = m_strFilter};
  __property bool             Filtered = { read = m_bFiltered};
  __property FilterID         ID       = { read = m_fid};
  __property CFilterStack*    Next     = { read = m_pfdNext};

 public:
  __fastcall CFilterStack( FilterID&        fid,
                           const AnsiString strFilter,
                           bool             bFiltered,
                           CFilterStack*    pvTopStack)
   : m_strFilter( strFilter), m_bFiltered( bFiltered), m_pfdNext( pvTopStack) { fid = m_fid = ++cm_fidDone;}
}; /* class CFilterStack */

#define FID_START 1234 // Valeur aléatoire (pas trop grande) de départ des ID de filtres
FilterID CFilterStack::cm_fidDone = FID_START; // Valeur aléatoire de départ

//********//
// CTable //
//********//////////////////////////////////////////////////////////////////////
//                                                                            //
//    Cette classe surcharge la classe VCL TTable pour lui adjoindre la       //
// gestion des tables EIC (Base de données et Nom de table) via les propriétés//
// EIC*, désactiver la gestion de définition de champs (qui ne gère pas les   //
// valeurs par défaut des champs) et ajouter des opérations de modification   //
// de structure, de copie de table et de gestion des filtres.                 //
//                                                                            //
// Note : Quelque soit le type d'une table CTable, on peut préciser son nom   //
//        complet directement (propriété "TableName"); toutefois, pour les    //
//        table de type non libre, si le code de la table est précisé, il     //
//        prévaut sur le nom de la table.                                     //
////////////////////////////////////////////////////////////////////////////////
/******************************************************************************\
************* CTable::CTable() *************************************************
********************************************************************************
*                                                                              *
*    Constructeur par défaut (utilisé pour les instances d'une fiche).         *
*                                                                              *
* Paramètres :                                                                 *
*   pOwner : Composant propriétéaire de la table.                              *
\******************************************************************************/
__fastcall CTable::CTable( TComponent* pOwner)
: TTable( pOwner)
{
 Init( ttDossier);
} /* CTable::CTable() */


/******************************************************************************\
************* CTable::CTable() *************************************************
********************************************************************************
*                                                                              *
*    Constructeur conseillé pour une table standard (de type non libre).       *
*                                                                              *
* Paramètres :                                                                 *
*   eTypeTable    : Type de la table (une valeur tt* - ttLibre déconseillé)    *
*   pcszCodeTable : Le code de la table                                        *
*   pOwner        : Composant propriétéaire de la table.                       *
*                                                                              *
* Note : Si ce constructeur est utilisé avec ttLibre, le nom complet de la     *
*        table sera exactement le code de la table.                            *
\******************************************************************************/
__fastcall CTable::CTable( TTypeTable  eTypeTable, // ttLibre déconseillé sinon TableName = EICCodeTable
                           const char* pcszCodeTable,
                           TComponent* pOwner     /* = Application */)
: TTable( pOwner)
{
 Init( eTypeTable);
 SetEICCodeTable( pcszCodeTable);
} /* CTable::CTable() */


/******************************************************************************\
************* CTable::CTable() *************************************************
********************************************************************************
*                                                                              *
*    Constructeur conseillé pour une table libre (de type non standard).       *
*                                                                              *
* Paramètres :                                                                 *
*   pcszTableName : Nom complet de la table.                                   *
*   eTypeTable    : Type de la table (une valeur tt* - ttLibre conseillé)      *
*   pOwner        : Composant propriétéaire de la table.                       *
*                                                                              *
* Note : Si ce constructeur est utilisé avec un type différent de ttLibre, le  *
*        code de la table ne sera pas initialisé.                              *
\******************************************************************************/
__fastcall CTable::CTable( const char* pcszTableName,
                           TTypeTable  eTypeTable, // ttLibre conseillé
                           TComponent* pOwner     /* = Application */)
: TTable( pOwner)
{
 Init( eTypeTable, pcszTableName);
} /* CTable::CTable() */


/*----------------------------------------------------------------------------*\
------------- CTable::Init() ---------------------------------------------------
--------------------------------------------------------------------------------
-                                                                              -
-    Cette procédure permet d'initialiser l'instance de la table.              -
-                                                                              -
- Paramètres :                                                                 -
-   eTypeTable    : Type de la table (une valeur tt*)                          -
-   pcszTableName : Nom complet de la table (NULL == EmptyStr).                -
\*----------------------------------------------------------------------------*/
void __fastcall CTable::Init( TTypeTable  eTypeTable,
                              const char* pcszTableName)
{
 m_pvFiltersStack    = NULL; // Pas de filtre sur la pile

 SetEICTypeTable( eTypeTable);
 TableName = pcszTableName;
} /* CTable::Init() */


/******************************************************************************\
************* CTable::~CTable() ************************************************
********************************************************************************
*                                                                              *
*    Destructeur d'une table qui permet de vider la pile des filtres associée  *
* à la table (en _DEBUG, un avertissement est donné si la pile n'est pas vide).*
\******************************************************************************/
__fastcall CTable::~CTable( void)
{
 #ifdef _DEBUG
  if( m_pvFiltersStack)
  {
    AnsiString    strStack;
    CFilterStack* pfd        = (CFilterStack*)m_pvFiltersStack;
    int           nNbFilters = 0;

    do {
      strStack += "\n\"" + pfd->Filter + AnsiString( (const char*)( pfd->Filtered ? "\" (On)" : "\" (Off)"));
      nNbFilters++;
    } while( ( pfd = pfd->Next) != NULL);

    EICMsgBox( ( AnsiString( nNbFilters) + " filtre(s) reste(nt) sur la pile :" + strStack).c_str(),
               ( "~CTable( \"" + EICComponentName( this) + "\")").c_str(),
               MB_ICONWARNING);
  }
 #endif
 while( m_pvFiltersStack)
   PopFilter( ((CFilterStack*)m_pvFiltersStack)->ID);
} /* CTable::~CTable() */


/*----------------------------------------------------------------------------*\
------------- CTable::SetEICCodeTable() ----------------------------------------
--------------------------------------------------------------------------------
-                                                                              -
-    Cette procédure permet d'initialiser le nom complet de la table en        -
- fonction de son code.                                                        -
-                                                                              -
- Paramètres :                                                                 -
-   strCodeTable : Code de la table (3 lettres dont les 2 premières sont un    -
-                  code module et la troisième un caractère interne au module).-
-                                                                              -
- Notes : * Une table libre ne peut pas avoir de code de table (dans ce cas,   -
-           il est assimilé comme nom complet de la table).                    -
-         * En mode _DEBUG et en conception, si la table n'est pas de type     -
-           libre et que le code ne parait pas conforme, un avertissement est  -
-           affiché (pour les informaticiens seulement).                       -
\*----------------------------------------------------------------------------*/
void __fastcall CTable::SetEICCodeTable( const AnsiString strCodeTable)
{
 if( m_eTypeTable == ttLibre)
 { // Code de table interdit => On le transfert directement dans le nom de la table
   TableName = strCodeTable;
 }
 else
 { // Composition du nom de la table EIC (et stockage du code)
   m_strCodeTable = strCodeTable.UpperCase();
   TableName = "REVAO" + m_strCodeTable + ".db";
   #ifdef _DEBUG
    if( ComponentState.Contains( csDesigning))
//      if( m_strCodeTable.IsEmpty())
//        EICAttention( "Attention:\nLe nom réel de la table sera celui précisé dans \"TableName\"."/*, "Code de table"*/);
//      if( m_strCodeTable.Length() != 3)
//        EICAttention( "Attention:\nLe code devrait être constitué de 3 caractères alphanum (\"$$\" étant accepté comme code module)."/*, "Code de table"*/);
      if( m_strCodeTable.Length() == 3)
      /*else*/ for( int c, i = ( (( m_strCodeTable.SubString( 1, 2) == "$$") || ( m_strCodeTable.SubString( 1, 2) == "$2")) ? 3 : 1); i <= 3; i++)
      { c = m_strCodeTable[ i];
        if( ( ( c < 'A') || ( c > 'Z')) && ( ( c < '0') || ( c > '9')))
        { EICAttention( "Attention:\nLe code de la table ne parait pas correct (alphanum seulement - \"$$\" et \"$2\" étant acceptés comme code module)."/*, "Code de table"*/);
          break;
        }
      }
   #endif
 }
} /* CTable::SetEICCodeTable() */


/*----------------------------------------------------------------------------*\
------------- CTable::SetEICTypeTable() ----------------------------------------
--------------------------------------------------------------------------------
-                                                                              -
-    Cette procédure permet d'initialiser le nom de la base de données de la   -
- table.                                                                       -
-                                                                              -
- Paramètres :                                                                 -
-   eTypeTable : Type de la table (il détermine le nom de la base de données). -
-                                                                              -
- Notes : Lorsqu'une table de vient de type libre, son éventuel code est       -
-         assimilé comme nom complet de la table et il devient vide.           -
\*----------------------------------------------------------------------------*/
void __fastcall CTable::SetEICTypeTable( TTypeTable eTypeTable)
{
 static const char* cszNomsAlias[] = { "EIC_COMMUN", "EIC_DOSSIER", "EIC_TMP", "", "EIC_COMMUN_LOCAL","EIC_TABLE_DOSSIERS"};

 ASSERT( ( eTypeTable == ttLibre) ||
         ( ( (int)eTypeTable >= 0) &&
           ( (int)eTypeTable < ARRAYSIZE( cszNomsAlias))));

 m_eTypeTable = eTypeTable;
 Exclusive = ( eTypeTable == ttTmp);
 if( eTypeTable != ttLibre)
 { // Table Commun, Dossier, Tmp ou CommunLocal
   // => Base de données spécifique
   DatabaseName = cszNomsAlias[ eTypeTable];

   // => Le nom de la table est inchangé (il est connu via l'éventuel code)
 }
 else
 { // Table libre
   // => Base de Données inchangée

   // => Pas de code table
   //    (le code existant est transféré directement dans le nom de la table si besoin)
   if( TableName.IsEmpty())
     TableName = m_strCodeTable;
   m_strCodeTable = EmptyStr;
 }
} /* CTable::SetEICTypeTable() */


/*============================================================================*\
============= CTable::FieldExists() ============================================
================================================================================
=                                                                              =
=    Cette fonction de classe permet de savoir si un champ (la casse devant    =
= exacte) existe dans la description BDE d'une table.                          =
=                                                                              =
= Paramètres :                                                                 =
=   pcpropsTbl  : Propriétés BDE de la table dans laquelle rechercher le champ.=
=   pcFldDescs  : Tableau des descriptions des champs de la table.             =
=   pcszFldName : Nom du champ à rechercher (caractères OEM, casse exacte).    =
=   iFld0Num    : Retourne l'indice (base zéro) du champ ou le "Nombre de      =
=                 champs de la table" + 1 si le champ n'est pas trouvé.        =
=                                                                              =
= Retour :                                                                     =
=   TRUE si le champ est trouvé; FALSE sinon.                                  =
= Remarque:                                                                    =
=   La table doit être fermée.                                                 =
\*============================================================================*/
bool __fastcall CTable::FieldExists( const CURProps* pcpropsTbl,
                                     const FLDDesc*  pcFldDescs,
                                     const char*     pcszFldName,
                                     Word*           iFld0Num)
{
 Word wTmp; // Variable de boucle

 ASSERT( pcpropsTbl && pcFldDescs && pcszFldName && *pcszFldName);

 *iFld0Num = 0;
 for( wTmp = pcpropsTbl->iFields;
      wTmp > 0;
      pcFldDescs++, (*iFld0Num)++, wTmp--)
   if( !strcmp( pcFldDescs->szName, pcszFldName))
   { // Le champ a été trouvé
     return( true);
   }
 // On retourne l'indice du champ qui pourrait être ajouté en fin de table
 ASSERT( *iFld0Num == pcpropsTbl->iFields);

 (*iFld0Num)++;
 return( false);
} /* CTable::FieldExists() */

/*============================================================================*\
============= CTable::IndexExists() ============================================
================================================================================
=                                                                              =
=    Cette fonction de classe permet de savoir si un index (la casse devant    =
= exacte) existe dans une table.                                               =
=                                                                              =
= Paramètres :                                                                 =
=   strIndexName  : Nom de l'index recherché                                   =
=                                                                              =
= Retour :                                                                     =
=   TRUE si l'index est trouvé; FALSE sinon.                                   =
= Remarque:                                                                    =
=   La table doit être fermée.                                                 =
\*============================================================================*/
bool __fastcall CTable::IndexExists( const AnsiString &strIndexName)
{

  bool bIndexExiste = false;
  TStringList *ListeIndex = NULL;
  ListeIndex = new TStringList;
  GetIndexNames(ListeIndex);
  bIndexExiste = (ListeIndex->IndexOf(strIndexName) != -1);
  delete ListeIndex;
  ListeIndex = NULL;
  return bIndexExiste;
} /* CTable::IndexExists() */

/*----------------------------------------------------------------------------*\
------------- CTable::GetBDEDescs() --------------------------------------------
--------------------------------------------------------------------------------
-                                                                              -
-    Cette procédure retourne diverses descriptions BDE de la table.           -
-                                                                              -
- Paramètres :                                                                 -
-   hdb         : Retourne le handle de la base de données associée à la table.-
-   tbldesc     :    "     la description BDE minimale de la table.            -
-   propsTbl    :    "     les propriétés BDE de la table.                     -
-   pFldDescs   :    "     le tableau (alloué avec une entrée en plus) de la   -
-                 descriptions des champs de la table (pour l'entrée           -
-                 additionnelle, seul l'indice de champ est initialisé).       -
-   pOps        : Retourne un tableau d'opérateurs associés aux champs alloués -
-                 et initialisé (champ additionnel compris) à crNOOP.          -
-   ppvchkDescs : Si != NULL en entrée, retourne un tableau (alloué avec une   -
-                 entrée en plus) de tous les contrôles de validité de la table-
-                                                                              -
- Notes : * La table doit être fermée.                                         -
-         * Les pointeurs passés en paramètre doivent être à NULL.             -
\*----------------------------------------------------------------------------*/
void __fastcall CTable::GetBDEDescs( hDBIDb&    hdb,
                                     CRTblDesc& tbldesc,
                                     CURProps&  propsTbl,
                                     FLDDesc*&  pFldDescs,
                                     CROpType*& pOps,
                                     VCHKDesc** ppvchkDescs) /* = NULL*/
{
 hDBICur   hCurTable;   // Curseur BDE sur la table
 Word      wNbFieldsP1; // Nb de champs de la table + 1
 EICTag    tag;
 Word      wTmp;        // Variables de boucle
 CROpType* pOpTmp;
 FLDDesc*  pfldDescTmp;

 ASSERT(!Active);
 ASSERT(pFldDescs == NULL);
 ASSERT(pOps == NULL);
 ASSERT(!ppvchkDescs || !*ppvchkDescs);


 try {
   // => Ouverture de la table
   tag = TAG( 10); Open();
   // => Lecture du curseur de la table
   tag = TAG( 11); hCurTable = Handle;
   // => Lecture de la description de tous les champs actuels de la table
   tag = TAG( 12); Dbtables::Check( Bde::DbiGetCursorProps( hCurTable, propsTbl));
   if (EICTypeTable == ttLibre)
    strcpy(propsTbl.szName,TableName.c_str());
   tag = TAG( 13); pfldDescTmp = pFldDescs = new FLDDesc[ wNbFieldsP1 = (Word)( propsTbl.iFields + 1)];
   tag = TAG( 14); Dbtables::Check( Bde::DbiGetFieldDescs( hCurTable, pFldDescs));

   // => Lecture éventuelle des contrôles de validité des champs
   if( ppvchkDescs)
   {
     VCHKDesc* pvchkDesc; // Un contrôle de validité de la table

     tag = TAG( 15); *ppvchkDescs = pvchkDesc = new VCHKDesc[ propsTbl.iValChecks + 1];
     for( tag = TAG( 16), wTmp = 0; wTmp < propsTbl.iValChecks; pvchkDesc++)
       Dbtables::Check( Bde::DbiGetVchkDesc( hCurTable, ++wTmp, pvchkDesc));
     memset( pvchkDesc, 0, sizeof( *pvchkDesc)); // RAZ du dernier contrôle (celui alloué en plus)
   }

   // => Sauvegarde du handle de la base de données associée à la table
   tag = TAG( 17); hdb = DBHandle;

   // => Fermeture de la table
   tag = TAG( 18); Close();
   // => Création et initialisation des opérateurs sur les champs
   //    ET Synchronisation de l'ordre des champs existant (par indiçage croissant)

   ASSERT( pfldDescTmp == pFldDescs);

   tag = TAG( 19); pOpTmp = pOps = new CROpType[ wNbFieldsP1];
   tag = TAG( 20);
   for( wTmp = 0; wTmp < wNbFieldsP1; )
   {
     *pOpTmp++              = crNOOP;
     pfldDescTmp++->iFldNum = ++wTmp;
   }

   // => Description de la table
   tag = TAG( 21);
   memset( &tbldesc, 0, sizeof( tbldesc));
   strcpy( tbldesc.szTblName, propsTbl.szName);
   strcpy( tbldesc.szTblType, propsTbl.szTableType);
   tbldesc.iFldCount  = propsTbl.iFields;
   tbldesc.pecrFldOp  = pOps;
   tbldesc.pfldDesc   = pFldDescs;
 } catch( Exception& e) {
   Close();
   throw EUser( tag, e);
 }
} /* CTable::GetBDEDescs() */


/*******************************************************************************
void __fastcall CTable::GetBDEDescs(hDBIDb *hdb, CRTblDesc *tblDesc, CURProps *propsTbl, VCHKDesc** ppvchkDescs)

Fonction :

Paramètres d'entrée :

Paramètres modifiés : tblDesc

Valeur retournée :

ATTENTION : Les tableaux tblDesc.pfldDesc et tblDesc.pecrFldOp sont alloués lors de l'appel de cette fonction
            et devront être désalloué par la fonction appelante.
*******************************************************************************/
void __fastcall CTable::GetBDEDescs(hDBIDb *hdb, CRTblDesc *tblDesc, CURProps *propsTbl, VCHKDesc** ppvchkDescs)
{
CURProps  propsTblTmp;
CROpType *pOpsInutilise = NULL;         // Opérations à effectuer sur les champs
FLDDesc*  pfldBaseInutilise = NULL;     // Description BDE du champ à modifier
ASSERT(hdb);
ASSERT(tblDesc);

GetBDEDescs(*hdb, *tblDesc, propsTblTmp, pfldBaseInutilise, pOpsInutilise, ppvchkDescs);

if (propsTbl)
   *propsTbl = propsTblTmp;
}


/******************************************************************************\
************* CTable::Copy() ***************************************************
********************************************************************************
*                                                                              *
*   Cette fonction permet de copier la table (avec toutes ses données) dans la *
* même base de données.                                                        *
*                                                                              *
* Paramètres:                                                                  *
*   tag          : TAG de localisation de l'appelant.                          *
*   pcszCopyName : Nom de la copie de la table (extension ".db" implicite).    *
*   bOverwrite   : TRUE pour écraser l'éventuelle table déjà existante.        *
*   hInstance    : Instance de l'appelant.                                     *
*                                                                              *
* Retour :                                                                     *
*   TRUE si la copie a été réalisée avec succès; FALSE sinon (i.e. la copie    *
* existe déjà et on ne veut pas l'écraser).                                    *
*                                                                              *
* Note : Si une erreur survient, une exception est levée.                      *
\******************************************************************************/
// Désactivation de l'avertissement "Non-const function _fastcall Dbtables::TDBDataSet::OpenDatabase() called for const object."
#pragma warn -ncf
bool __fastcall CTable::Copy( const EICTag tag,
                              const char*  pcszCopyName,
                              bool         bOverwrite /* = false */,
                              int          hInstance  /* = HInstance*/)
const
{
 bool       bMustCloseDB; // TRUE s'il faut refermer la base de données associée à la table après la copie
 bool       bOk;          // TRUE si la copie s'est bien passée
 DBIResult  eResult;      // Résultat de la copie
// TDatabase* pdb;          // Base de données associée à la table

 ASSERT( pcszCopyName);
 ASSERT( *pcszCopyName);

 bOk          = false;
 bMustCloseDB = false;


 try {
   // => Ouverture de la base de données
   if(!Active)// ( ( pdb = Database) == NULL) || !pdb->Connected)
   { // La base de données de la table n'est pas déjà active => on l'ouvre
     Open();
//     pdb          = OpenDatabase();
     bMustCloseDB = true;
//     Database->Open();
   }
//   ASSERT( pdb);
//   ASSERT( pdb->Connected);

   // => Création de la table cible
   eResult = Bde::DbiCopyTable( Database->Handle, bOverwrite, TableName.c_str(), szPARADOX, (char*)pcszCopyName);
   switch (eResult)
    {
      case DBIERR_FILEEXISTS:
        if (!bOverwrite)
          break;                         //La table existait déjà, mais on ne veut pas l'écraser, on ignore l'erreur
      default:
        Dbtables::Check( eResult);       // Test du succès de la copie
    }
/*La version d'origine est quand même moins simple que les lignes précédentes !
   for(;;)
   {
     if( ( eResult = Bde::DbiCopyTable( pdb->Handle,
                                        bOverwrite, TableName.c_str(), szPARADOX,
                                        (char*)pcszCopyName)) == DBIERR_FILEEXISTS)
       if( !bOverwrite)
       { // La table existe déjà mais l'appelant ne veut pas l'écraser
         // => Notification
         break;
       }

     // Test du succès de la copie
     Dbtables::Check( eResult);
     break;
   }*/

   // => Notification du succès à l'appelant
   bOk = true;

   // => Fermeture de la base de données
   if( bMustCloseDB)
     /*pdb->*/Close();
 } catch( Exception& e) {
   if( bMustCloseDB)
     /*pdb->*/Close();
   throw EUser( tag, e);
 }
 return( bOk);
} /* CTable::Copy() */
// Réactivation de l'avertissement "Non-const function ? called for const object."
#pragma warn .ncf



bool __fastcall CTable::CopyTable(const AnsiString &TableSource,const AnsiString &TableDestination,bool bOverwrite)
{
  DBIResult eResult;

  ASSERT(Session->DatabaseCount);  //On doit avoir au moins un ALIAS EIC

  eResult = Bde::DbiCopyTable(Session->Databases[0]->Handle,bOverwrite,TableSource.c_str(),szPARADOX,TableDestination.c_str());
  switch (eResult)
    {
      case DBIERR_FILEEXISTS:
        if (!bOverwrite)
          break;                         //La table existait déjà, mais on ne veut pas l'écraser, on ignore l'erreur
      default:
        Dbtables::Check( eResult);       // Test du succès de la copie
    }
  return true;
}



/******************************************************************************\
************* CTable::AddField() ***********************************************
********************************************************************************
*                                                                              *
*    Cette fonction permet de créer un nouveau champ dans la table.            *
*                                                                              *
* Paramètres :                                                                 *
*   tag             : Tag dans l'appelant.                                     *
*   pcszFldName     : Nom du champ à ajouter (caractères OEM).                 *
*   efldPDXType     : Type du champ à rajouter (une valeur restreinte fldPDX*).*
*   wInsertPos0     : Position (base 0) du champ dans la table (>=NbChamps=fin)*
*   ushMaxStringLen : Longueur maximale du champ (pour fldPDXCHAR seulement).  *
*   bObligatoire    : TRUE si la valeur du champ doit être obligatoire.        *
*   pvDefaultValue  : Valeur par défaut du champ (NULL = aucune)               *
*   hInstance       : Instance de l'appelant                                   *
*                                                                              *
* Retour :                                                                     *
*   TRUE si le champ a bien été inséré; FALSE s'il existe déjà dans la table.  *
*                                                                              *
* Notes : * Si le champ existe déjà, ses éventuels contrôles de validité ne    *
*           sont pas modifiés par cette fonction.                              *
*         * Un champ obligatoire doit avoir une valeur par défaut sinon la     *
*           restructuration va générer des doublons (car la nouvelle colonne   *
*           ajoutée sera vide).                                                *
*         * Un champ date ne peut pas avoir de valeur par défaut.              *
\******************************************************************************/
bool __fastcall CTable::AddField( const EICTag   tag,
                                  const char*    pcszFldName,
                                  Word           efldPDXType,     // Une valeur fldPDX* restreinte
                                  Word           wInsertPos0,     // = 0xFFFF
                                  unsigned short ushMaxStringLen, // = 0 (Ignoré si efldPDXType != fldPDXCHAR)
                                  bool           bObligatoire,    // = false
                                  void*          pvDefaultValue,  // = NULL
                                  int            hInstance)       // = (int)HInstance
{
 hDBIDb    hdb;          // Handle de la base de données associée à la table
 CRTblDesc tblDesc;      // Description BDE de la table
 CURProps  propsTbl;     // Propriétés de la table
 FLDDesc*  pFldDescs;    // Descriptions de champs de la table
 CROpType* pOps;         // Opérations à effectuer sur les champs

 Word      iFld0Num;     // Indice (en base 0) du champ à rajouter dans la table
 bool      bCreated;     // TRUE si le champ a bien été créé; FALSE s'il existe déjà

 FLDDesc   fldDesc;      // Description BDE du champ à rajouter
 VCHKDesc  vchkDesc;     // Contrôle de validité sur le champ à rajouter
 CROpType  ecrAddVChkOp; // Opérateur d'ajout du contrôle de validité du nouveau champ

 Word      wTmp;         // Variables de boucles
 FLDDesc*  pFldDescTmp;
 CROpType* pOpTmp;
 EICTag    tagTmp;

 ASSERT( !Active);
 ASSERT( pcszFldName && *pcszFldName && ( strlen( pcszFldName) < sizeof( fldDesc.szName)));
// ASSERT( !bObligatoire || pvDefaultValue); // Un champ obligatoire doit avoir une valeur par défaut (sur une table qui contient au moins 1 enregistrement)
 ASSERT( ( efldPDXType == fldPDXSHORT) || ( efldPDXType == fldPDXLONG) || ( efldPDXType == fldPDXNUM) ||
         ( efldPDXType == fldPDXMONEY) || ( efldPDXType == fldPDXAUTOINC) ||
         ( ( efldPDXType == fldPDXDATE) && !pvDefaultValue) || (efldPDXType == fldPDXBOOL) ||
         (efldPDXType == fldPDXDATETIME) ||  (efldPDXType == fldPDXMEMO)  ||(efldPDXType == fldPDXBINARYBLOB)  ||
         ( ( efldPDXType == fldPDXCHAR) && ( ushMaxStringLen > 0) &&
           ( !pvDefaultValue || ( (unsigned short)strlen( (const char*)pvDefaultValue) <= ushMaxStringLen))));

 pFldDescs = NULL;
 pOps      = NULL;
 bCreated  = false;


 try {
 try {
   tagTmp = TAG( 30); GetBDEDescs( hdb, tblDesc, propsTbl, pFldDescs, pOps);
   if( !FieldExists( &propsTbl, pFldDescs, pcszFldName, &iFld0Num))
   {
      ASSERT( iFld0Num == propsTbl.iFields + 1);
     // => Initialisation de la description du nouveau champ
     memset( &fldDesc, 0, sizeof( fldDesc));
     strcpy( fldDesc.szName, pcszFldName);
     fldDesc.iFldNum  = iFld0Num;
     fldDesc.iFldType = efldPDXType;
     if( efldPDXType == fldPDXCHAR || efldPDXType == fldPDXMEMO)
     { // Champ chaîne
       // => Initialisation de la longueur max du champ
       fldDesc.iUnits1 = (short)ushMaxStringLen;
     }
    /*
     fldDesc.iSubType = efldst???;
     //fldDesc.iUnits1;
     fldDesc.iUnits2;
     fldDesc.iOffset;
     fldDesc.iLen;
     fldDesc.iNullOffset;
     fldDesc.efldvVchk;
     fldDesc.efldrRights;
     fldDesc.bCalcField;
     fldDesc.iUnUsed[2];
    */
     // => Initialisation
     if( wInsertPos0 > propsTbl.iFields)
       wInsertPos0 = propsTbl.iFields;

     // => Resynchronisation de l'ordre des champs qui suivront le nouveau champ
     // Pour les anciens champs qui succèderont le nouveau :
     //  - opération de modification du champ
     //  - décalage vers la droite (les champs sont modifiés au niveau de leur position)
     //  - (les indices de champ sont déjà initialisés de nInsertPos0+1 à propsTbl.iFields)
//     tagTmp = TAG( 30);
     for( pFldDescTmp = pFldDescs + propsTbl.iFields, pOpTmp = pOps + propsTbl.iFields, wTmp = propsTbl.iFields;
                                                                                        wTmp > wInsertPos0;
          pFldDescTmp--,                              pOpTmp--,                         wTmp--)
     {
       *pOpTmp      = crMODIFY;
       *pFldDescTmp = *( pFldDescTmp - 1);
       ASSERT( pFldDescTmp->iFldNum == wTmp);
     }

     // => Insertion du Nouveau champ (indice déjà initialisé à propsTbl.iFields + 1)
//     tagTmp = TAG( 32);
     ASSERT( ( pOpTmp      == pOps      + wInsertPos0) &&
             ( pFldDescTmp == pFldDescs + wInsertPos0));
     *pOpTmp      = crADD;
     *pFldDescTmp = fldDesc;

     // => Adjonction éventuelle du contrôle de validité sur le nouveau champ
     if( bObligatoire || pvDefaultValue)
     {
       memset( &vchkDesc, 0, sizeof( vchkDesc));
       vchkDesc.iFldNum   = (Word)( wInsertPos0 + 1); // Position (à partir de 1, dans le tableau de description des champs)
       vchkDesc.bRequired = bObligatoire;

       if( pvDefaultValue)
       {
         int nSizeofDefVal;

         vchkDesc.bHasDefVal = true;
         switch( efldPDXType)
         { case fldPDXSHORT : nSizeofDefVal = 2;                                    break;
           case fldPDXLONG  :
           case fldPDXAUTOINC  :
           nSizeofDefVal = 4;                                    break;
           //case fldPDXDATE  : => Valeur par défaut interdite
           case fldPDXMONEY :
           case fldPDXNUM   : nSizeofDefVal = 8;                                    break;
           case fldPDXCHAR  : nSizeofDefVal = strlen( (const char*)pvDefaultValue);
                              if( nSizeofDefVal <= sizeof( vchkDesc.aDefVal))
                                break;
           case fldPDXBOOL  : nSizeofDefVal = sizeof( bool); break;
           default          : EICThrowInvalidArgument( TAG( 33));                   break;
         }
         memcpy( &vchkDesc.aDefVal, pvDefaultValue, nSizeofDefVal);
       }
       else
         ASSERT( !vchkDesc.bHasDefVal);

       ecrAddVChkOp         = crADD;

       tblDesc.pecrValChkOp = &ecrAddVChkOp;
       tblDesc.pvchkDesc    = &vchkDesc;
       tblDesc.iValChkCount = 1;
     }
     else
     { // Pas de contrôle de validité à gérer
       ASSERT( tblDesc.iValChkCount == 0);
     }
     // => Description de la table à restructurer
     ASSERT( !strcmp( tblDesc.szTblName, propsTbl.szName) &&
             !strcmp( tblDesc.szTblType, propsTbl.szTableType) &&
             ( tblDesc.iFldCount == propsTbl.iFields) &&
             ( tblDesc.pecrFldOp == pOps) &&
             ( tblDesc.pfldDesc == pFldDescs));
     tblDesc.iFldCount++;

     // => Ajout dans la table
     tagTmp = TAG( 31);
     Dbtables::Check( Bde::DbiDoRestructure( hdb, 1, &tblDesc, NULL, NULL, NULL, false));

     // => Notification à l'appelant
     bCreated = true;
   } // Ajout du nouveau champ qui n'existait pas encore
   delete[] pOps;      pOps = NULL;
   delete[] pFldDescs;
 } catch( Exception& e) {
   delete[] pOps;
   delete[] pFldDescs;
   throw EUser( tagTmp, e);
 }
 } catch( Exception& e) {
   throw EUser( tag, e);
 }
 #ifdef _DEBUG
  if( bCreated)
  { pOps      = NULL;
    pFldDescs = NULL;
    GetBDEDescs( hdb, tblDesc, propsTbl, pFldDescs, pOps);
    ASSERT( FieldExists( &propsTbl, pFldDescs, pcszFldName, &iFld0Num));
    delete[] pOps;
    delete[] pFldDescs;
  }
 #endif
 return( bCreated);
} /* CTable::AddField() */


/******************************************************************************\
************* CTable::DropField() ***********************************************
********************************************************************************
*                                                                              *
*    Cette fonction permet de supprimer un champ NON INDEXÉ de la table.       *
*                                                                              *
* Paramètres :                                                                 *
*   tag         : Tag dans l'appelant.                                         *
*   pcszFldName : Nom du champ à supprimer (caractères OEM).                   *
*   hInstance   : Instance de l'appelant                                       *
*                                                                              *
* Retour :                                                                     *
*   TRUE si le champ a bien été supprimé; FALSE s'il n'existe pas dans la table*
*                                                                              *
* Note : Si le champ n'existe pas, la fonction ne fait rien et retourne FALSE. *
\******************************************************************************/
bool __fastcall CTable::DropField( const EICTag tag,
                                   const char*  pcszFldName,
                                   int          hInstance /* = (int)HInstance */)
{
  ASSERT( pcszFldName);

  EICTag    nTag;
  hDBIDb    hdb;          // Handle de la base de données associée à la table
  CRTblDesc tblDesc;      // Description BDE de la table
  CURProps  propsTbl;     // Propriétés de la table
  FLDDesc  *pFldDescs;    // Descriptions de champs de la table
  CROpType *pOps;         // Opérations à effectuer sur les champs
  Word      iFld0Num;     // Indice (en base 0) du champ à supprimer de la table

  Word      wCntFields;   // Nouveau nombre de champs de la table
  FLDDesc*  pFldDescsNew; // Nouvelles descriptions de champs de la table
  CROpType* pOpsNew;      // Opérations à effectuer sur les champs

  bool      bRemoved;

  try {
                     bRemoved     = false;
                     pFldDescs    = NULL;
                     pOps         = NULL;
                     pFldDescsNew = NULL;
                     pOpsNew      = NULL;
    nTag = TAG( 40); GetBDEDescs( hdb, tblDesc, propsTbl, pFldDescs, pOps);
                     if ( FieldExists( &propsTbl, pFldDescs, pcszFldName, &iFld0Num)) {
                       ASSERT( iFld0Num<=propsTbl.iFields);
                       wCntFields = ( Word)( propsTbl.iFields - 1);
                       pFldDescsNew = new FLDDesc [wCntFields];
                       pOpsNew      = new CROpType[wCntFields];
                       for( int i=0; i<wCntFields; i++) pOpsNew[i]=crNOOP;
                       // Recopie la nouvelle structure
                       int j = 0;
                       for( int i=0; i<propsTbl.iFields; i++)
                         if ( i!=iFld0Num) pFldDescsNew[j++] = pFldDescs[i];

    nTag = TAG( 41);   tblDesc.iFldCount = wCntFields;
                       tblDesc.pecrFldOp = pOpsNew;
                       tblDesc.pfldDesc  = pFldDescsNew;
                       Dbtables::Check( Bde::DbiDoRestructure( hdb, 1, &tblDesc, NULL, NULL, NULL, false));
                       bRemoved = true;
                     }

  } catch( Exception &e) {
    delete []pFldDescs;
    delete []pOps;
    delete []pFldDescsNew;
    delete []pOpsNew;
    throw EUser( nTag, e);
  }

  delete []pFldDescs;
  delete []pOps;
  delete []pFldDescsNew;
  delete []pOpsNew;

#ifdef _DEBUG
  // Vérifie que le champ à bien été détruit
  pOps      = NULL;
  pFldDescs = NULL;
  GetBDEDescs( hdb, tblDesc, propsTbl, pFldDescs, pOps);
  ASSERT( !FieldExists( &propsTbl, pFldDescs, pcszFldName, &iFld0Num));
  delete[] pOps;
  delete[] pFldDescs;
#endif

  return bRemoved;
} /* CTable::DropField() */


/******************************************************************************\
************* CTable::ChangeFieldType() ****************************************
********************************************************************************
*                                                                              *
*    Cette fonction permet de changer le type d'un champ d'une table.          *
*                                                                              *
* Paramètres :                                                                 *
*   tag            : Tag dans l'appelant.                                      *
*   pcszFldName    : Nom du champ à modifier (caractères OEM).                 *
*   efldPDXType    : Nouveau type du champ (une valeur restreinte fldPDX*).    *
*   ushMaxDataLen  : Longueur maximale des données du champ (pour fldPDXMEMO et*
*                    fldPDXCHAR seulement).                                    *
*   hInstance      : Instance de l'appelant                                    *
*                                                                              *
* Retour :                                                                     *
*   TRUE si le type du champ a bien été modifié; FALSE s'il est déjà du type   *
* (et éventuellement de la taille) indiqué en paramètre.                       *
*                                                                              *
* Notes : - Si le champ n'existe pas, la fonction ne fait rien et renvoi FALSE.*
*         - Les conversions sont automatiques (les valeurs numériques dont     *
*           l'expression convertie en caractères qui sont trop longues sont    *
*           mises à NULL dans la valeur résultant de la conversion; les valeurs*
*           caractère (et mémo caractères) sont tronquées; les valeurs Date et *
*           DateHeure sont tronquées bizarrement...).                          *
*         - Attention aux conversions automatiques sur un champ indexé : elles *
*           peuvent conduire à la production de doublons ou peuvent lever une  *
*           exception s'il s'agit d'un champ de l'index primaire...            *
*         - La conversion ne se fait actuellement que vers des champs          *
*           fldPDXMEMO, fldPDXCHAR, fldPDXNUM et fldPDXLONG
*           SI la conversion que vous désirez n'est pas disponible, il         *
*           vous suffit de changer un ASSERT et un If, puis de vérifier le bon *
*           fonctionnement de la conversion.                                   *
*                                                                              *
\******************************************************************************/
bool __fastcall CTable::ChangeFieldType( EICTag         tag,
                                         const char*    pcszFldName,
                                         Word           efldPDXType,   // Une valeur fldPDX* restreinte
                                         unsigned short ushMaxDataLen, // = 0xFFFF
                                         int            hInstance)     // = (int)HInstance
{
 hDBIDb    hdb;          // Handle de la base de données associée à la table
 CRTblDesc tblDesc;      // Description BDE de la table
 CURProps  propsTbl;     // Propriétés de la table
 FLDDesc*  pFldDescs;    // Descriptions de champs de la table
 CROpType* pOps;         // Opérations à effectuer sur les champs

 bool      bChanged;     // TRUE si le type du champ a bien été changé; FALSE si le champ est déjà du type désiré

 Word      iFld0Num;     // Indice (en base 0) du champ à changer dans la table
 FLDDesc*  pfldDesc;     // Description BDE du champ à modifier

 ASSERT( !Active);
 ASSERT( pcszFldName && *pcszFldName && ( strlen( pcszFldName) < sizeof( pFldDescs->szName)));
 ASSERT((efldPDXType == fldPDXMEMO && ( ushMaxDataLen >= 1) && ( ushMaxDataLen <= 240)) ||
        (efldPDXType == fldPDXCHAR && ( ushMaxDataLen >= 1) && ( ushMaxDataLen <= 255)) ||
        (efldPDXType == fldPDXBINARYBLOB && ( ushMaxDataLen >= 1) && ( ushMaxDataLen <= 240)) ||
        efldPDXType == fldPDXNUM ||
        efldPDXType == fldPDXLONG ||
        efldPDXType == fldPDXSHORT ||
        efldPDXType == fldPDXDATETIME ||
        efldPDXType == fldPDXAUTOINC);

 pFldDescs = NULL;
 pOps      = NULL;
 bChanged  = false;


 try {
   GetBDEDescs( hdb, tblDesc, propsTbl, pFldDescs, pOps);
   if( FieldExists( &propsTbl, pFldDescs, pcszFldName, &iFld0Num))
   {
     ASSERT( iFld0Num <= propsTbl.iFields);

     pfldDesc = pFldDescs + iFld0Num;
     ASSERT( pfldDesc->efldvVchk == fldvNOCHECKS); // Changement de type de champ avec contrôle de validité non implémenté !!!
     //SI le type cible a déjà été testé par quelqu'un => on autorise la conversion
     if((((pfldDesc->iFldType != fldBLOB) || ( pfldDesc->iSubType != fldstMEMO)) && efldPDXType == fldPDXMEMO) ||     // Changement de type vers un Mémo
        (((pfldDesc->iFldType != fldZSTRING) || ( pfldDesc->iUnits1 != ushMaxDataLen)) && efldPDXType == fldPDXCHAR)|| // Changement de type vers un ZString
        (((pfldDesc->iFldType != fldBLOB) || ( pfldDesc->iSubType != fldstBINARY)) && efldPDXType == fldPDXBINARYBLOB) ||     // Changement de type vers un Mémo
        (pfldDesc->iFldType == fldINT32 && efldPDXType == fldPDXNUM) ||   //Changement vers un Double
        (pfldDesc->iFldType == fldFLOAT && efldPDXType == fldPDXLONG)||   //Changement vers un Long
        (pfldDesc->iFldType == fldBOOL && efldPDXType == fldPDXSHORT)||   //Bool vers Short
        (pfldDesc->iFldType == fldINT16 && efldPDXType == fldPDXLONG)|| //Short vers Long
        (pfldDesc->iFldType == fldDATE && efldPDXType == fldPDXDATETIME) ||
        (pfldDesc->iFldType == fldINT32 && efldPDXType == fldPDXAUTOINC)) // Date vers DateTime
       {
       pfldDesc->iFldType = efldPDXType;
       pfldDesc->iUnits1  = ushMaxDataLen;
/*
       fldDesc.iSubType = efldst???;
       //fldDesc.iUnits1;
       fldDesc.iUnits2;
       fldDesc.iOffset;
       fldDesc.iLen;
       fldDesc.iNullOffset;
       fldDesc.efldvVchk;
       fldDesc.efldrRights;
       fldDesc.bCalcField;
       fldDesc.iUnUsed[2];
*/
       *( pOps + iFld0Num) = crMODIFY;

       // => Description de la table à restructurer
       ASSERT( !strcmp( tblDesc.szTblName, propsTbl.szName) &&
               !strcmp( tblDesc.szTblType, propsTbl.szTableType) &&
               ( tblDesc.iFldCount == propsTbl.iFields) &&
               ( tblDesc.pecrFldOp == pOps) &&
               ( tblDesc.pfldDesc == pFldDescs));

       // => Ajout dans la table
//       tagTmp = TAG( 50);
       Dbtables::Check( Bde::DbiDoRestructure( hdb, 1, &tblDesc, NULL, NULL, NULL, false));

       // => Notification
       bChanged = true;
       }
   }
   delete[] pOps;      pOps = NULL;
   delete[] pFldDescs;
 } catch( Exception& e) {
   delete[] pOps;
   delete[] pFldDescs;
   throw EUser( tag, e);
 }
 return( bChanged);
} /* CTable::ChangeFieldType() */


/******************************************************************************\
************* CTable::ChangeValChk() *******************************************
********************************************************************************
*                                                                              *
*    Cette fonction permet de modifier les contrôles de validité d'un champ de *
* la table.                                                                    *
*                                                                              *
* Paramètres :                                                                 *
*   tag            : Tag dans l'appelant.                                      *
*   pcszFldName    : Nom du champ dont on veut modifier un/des contrôle(s) de  *
*                    validité (caractères OEM).                                *
*   bObligatoire   : TRUE si la valeur du champ doit devenir obligatoire.      *
*   pvDefaultValue : Nouvelle valeur par défaut du champ (NULL = aucune)       *
*   hInstance      : Instance de l'appelant                                    *
*                                                                              *
* Retour :                                                                     *
*   TRUE si au moins un contrôle de validité du champ a été modifié; FALSE     *
* sinon (i.e. les contrôles existant sont identiques aux nouveaux).            *
*                                                                              *
* Note : Si le champ dont on veut modifier un/des contrôles de validité est    *
*        introuvable dans la table, une exception est levée.                   *
\******************************************************************************/
bool __fastcall CTable::ChangeValChk( EICTag      tag,
                                      const char* pcszFldName,
                                      bool        bObligatoire,   // = false,
                                      void*       pvDefaultValue, // = NULL,
                                      int         hInstance,      // = (int)HInstance
                                      void*       pvMinValue, // = NULL,
                                      void*       pvMaxValue) // = NULL,
{
 hDBIDb    hdb;           // Handle de la base de données associée à la table
 CRTblDesc tblDesc;       // Description BDE de la table
 CURProps  propsTbl;      // Propriétés de la table
 FLDDesc*  pFldDescs;     // Descriptions de champs de la table
 CROpType* pOps;          // Opérations à effectuer sur les champs
 VCHKDesc* pvchkDescs;    // Contrôles de validité des champs de la table (plus un)
 Word      iFld0Num;      // Indice (en base 0) du champ dont on veut modifier les contrôles de validité

 Word      iFldNum;       //   "    (en base 1) "    "    "   "   "      "      "      "     "     "
 bool      bChanged;      // TRUE si le contrôle de validité du champ a été modifié
 FLDDesc*  pFldDesc;      // La description du champ à modifier
 short     nSizeofDefVal;//,nSizeofMinVal,nSizeofMaxVal; // Taille binaire de la valeur par défaut du champ
 VCHKDesc* pvchkDesc;     // Contrôles (nouveau ou existant) de validité du champ
 CROpType  ecropVChk;     // Opérateur sur le contrôle de validité du champ

 bool      bOldDefVal,bOldMinVal,bOldMaxVal;    // TRUE si  l'ancien  contrôle de validité comporte une valeur par défaut
 bool      bNewDefVal,bNewMinVal,bNewMaxVal;    //  "   "  le nouveau    "     "     "        "      "    "     "    "
 Word      wTmp;          // Indice de boucle

 ASSERT( !Active);
 ASSERT( pcszFldName);
 ASSERT(*pcszFldName);
 ASSERT(strlen(pcszFldName) < sizeof(pFldDescs->szName));

 ecropVChk  = crADD; // A priori, on va ajouter un contrôle de validité au champ
 pFldDescs  = NULL;
 pOps       = NULL;
 pvchkDescs = NULL;
 bChanged   = false;
 try {
   GetBDEDescs( hdb, tblDesc, propsTbl, pFldDescs, pOps, &pvchkDescs);
   if( !FieldExists( &propsTbl, pFldDescs, pcszFldName, &iFld0Num))
   { // Erreur: Le champ n'existe pas !!!
     EICThrowInvalidArgument( TAG( 60));
   }

   ASSERT( iFld0Num <= propsTbl.iFields);

//   tagTmp = TAG( 61);
   iFldNum = (Word)( iFld0Num + 1);
   for( pvchkDesc = pvchkDescs, wTmp = propsTbl.iValChecks; wTmp; wTmp--, pvchkDesc++)
     if( pvchkDesc->iFldNum == iFldNum)
     { // Le champ a déjà un contrôle de validité
       // => On va (peut-être) le modifier
       ecropVChk = crMODIFY;
       break;
     }
   // Initialisations
   ASSERT( ( ( ecropVChk == crADD) && !pvchkDesc->bRequired && !pvchkDesc->bHasDefVal) ||
           ( ecropVChk == crMODIFY));

   bOldDefVal = ( pvchkDesc->bHasDefVal != 0);
   bNewDefVal = ( pvDefaultValue != NULL);
   bOldMinVal = ( pvchkDesc->bHasMinVal != 0);
   bNewMinVal = ( pvMinValue != NULL);
   bOldMaxVal = ( pvchkDesc->bHasMaxVal != 0);
   bNewMaxVal = ( pvMaxValue != NULL);
   if( ( pFldDesc = pFldDescs + iFld0Num)->iFldType == fldZSTRING)
     if( bNewDefVal)
     { // Chaînes par défaut à comparer, modifier ou initialiser => La taille est celle de la nouvelle (compris le '\0' terminal)
       nSizeofDefVal = (short)( strlen( (const char*)pvDefaultValue) + 1);
     }
     else
     { // Pas de valeur par défaut => Valeur par défaut = taille max de la chaîne
       nSizeofDefVal = pFldDesc->iLen;
     }
   else
   { // Champ non chaîne => Valeur par défaut = taille binaire du champ
     nSizeofDefVal = pFldDesc->iLen;
   }
/*   nSizeofMinVal = nSizeofDefVal;
   nSizeofMaxVal = nSizeofDefVal;*/
   if( ( ( ecropVChk == crADD) && ( bObligatoire || bNewDefVal)
       ) // On va ajouter un/des contrôle(s) de validité
       ||
       ( ( ( pvchkDesc->bRequired != 0) ^ bObligatoire) ||
         ( bOldDefVal ^ bNewDefVal) ||
         ( bOldDefVal && bNewDefVal && memcmp( pvchkDesc->aDefVal, pvDefaultValue, nSizeofDefVal)) ||
         ( bOldMinVal ^ bNewMinVal) ||
         ( bOldMinVal && bNewMinVal && memcmp( pvchkDesc->aMinVal, pvMinValue, nSizeofDefVal)) ||
         ( bOldMaxVal ^ bNewMaxVal) ||
         ( bOldMaxVal && bNewMaxVal && memcmp( pvchkDesc->aMaxVal, pvMaxValue, nSizeofDefVal))
       ) // On va faire des modifications effectives
     )
   {
      // Il faut modifier les contrôles de validité du champ en...
//     tagTmp = TAG( 62);
     if( ( ecropVChk != crADD) && !bObligatoire && !bNewDefVal && !bNewMinVal && !bNewMaxVal)
     { // ...supprimant les contrôles de validité existant
       ecropVChk = crDROP;
     }
     else
     { // ...créant ou modifiant des contrôles de validité
       ASSERT( ( ecropVChk == crADD) || ( pvchkDesc->iFldNum == iFldNum));

       pvchkDesc->iFldNum    = iFldNum;
       pvchkDesc->bRequired  = bObligatoire;
       pvchkDesc->bHasDefVal = bNewDefVal;
       if (bNewDefVal)
         memcpy(pvchkDesc->aDefVal,pvDefaultValue,nSizeofDefVal);
       pvchkDesc->bHasMinVal = bNewMinVal;
       if (bNewMinVal)
         memcpy(pvchkDesc->aMinVal,pvMinValue,nSizeofDefVal);
       pvchkDesc->bHasMaxVal = bNewMaxVal;
       if (bNewMaxVal)
         memcpy(pvchkDesc->aMaxVal,pvMaxValue,nSizeofDefVal);
     }

     // Notification à la table
     tblDesc.pecrValChkOp = &ecropVChk;
     tblDesc.pvchkDesc    = pvchkDesc;
     tblDesc.iValChkCount = 1;

     ASSERT( !strcmp( tblDesc.szTblName, propsTbl.szName) &&
             !strcmp( tblDesc.szTblType, propsTbl.szTableType) &&
             ( tblDesc.iFldCount == propsTbl.iFields) &&
             ( tblDesc.pecrFldOp == pOps) &&
             ( tblDesc.pfldDesc == pFldDescs));

//     tagTmp = TAG( 64);
     Dbtables::Check( Bde::DbiDoRestructure( hdb, 1, &tblDesc, NULL, NULL, NULL, false));
     // => Notification à l'appelant
     bChanged = true;
   }
   delete[] pvchkDescs; pvchkDescs = NULL;
   delete[] pOps;       pOps       = NULL;
   delete[] pFldDescs;
 } catch( Exception& e) {
   delete[] pvchkDescs;
   delete[] pOps;
   delete[] pFldDescs;
   throw EUser( tag, e);
 }
 return( bChanged);
} /* CTable::ChangeValChk() */


/******************************************************************************\
************* CTable::UpgradeLevel() *******************************************
********************************************************************************
*                                                                              *
*    Cette procédure permet de mettre à jour le niveau de la table (i.e. sa    *
* version paradox) au niveau le plus élevé reconnu par le BDE (actuellement 7).*
*                                                                              *
* Paramètres :                                                                 *
*   tag       : Tag dans l'appelant.                                           *
*   hInstance : Instance de l'appelant                                         *
*                                                                              *
* Note : Si la mise à niveau de la table ne peut pas être réalisée, une        *
*        exception est levée.                                                  *
\******************************************************************************/
void __fastcall CTable::UpgradeLevel( const EICTag tag,
                                      int          hInstance) // = (int)HInstance
{
 bool      bExclusiveOk;    // TRUE si la valeur de bExclusive est valide
 bool      bIsExclusive;    // TRUE si la table est en accès exclusif
 hDBICur   hCurTable;       // Curseur BDE sur la table
 int       nLevel;          // Niveau actuel de la table
 hDBIDb    hDb;             // Handle de la base de données associée à la table
 CURProps  propsTbl;        // Propriétés de la table
 CRTblDesc tblDesc;         // Description BDE de la table
 FLDDesc   optParams;       // Paramètre de description de changement de niveau
 char      szLevel[] = "7"; // Niveau vers lequel mettre à jour la table
 EICTag    tagTmp;

 ASSERT( !Active);

 bExclusiveOk = false;


 try {
 try {
   // => Stockage des propriétés critiques qui vont être modifiées
   tagTmp = TAG( 70);
   bIsExclusive = Exclusive;
   bExclusiveOk = true;

   // => Ouverture de la table
   tagTmp = TAG( 71); Exclusive = true;
   tagTmp = TAG( 72); Open();

   // => Lecture du curseur de la table
   tagTmp = TAG( 73); hCurTable = Handle;

   // => Lecture du niveau actuel de la table
   tagTmp = TAG( 74); nLevel = TableLevel;
   ASSERT( nLevel <= 7);

   // => Lecture des propriétés BDE de la table
   tagTmp = TAG( 75); Dbtables::Check( Bde::DbiGetCursorProps( hCurTable, propsTbl));

   // => Sauvegarde du handle de la base de données associée à la table
   tagTmp = TAG( 76); hDb = DBHandle;

   // => Fermeture de la table
   tagTmp = TAG( 77); Close();

   // => Restauration des propriétés critiques
   tagTmp = TAG( 78); Exclusive = bIsExclusive;
   bExclusiveOk = false;
   if( nLevel < 7)
   { // => Description BDE de la table
     memset( &tblDesc, 0, sizeof( tblDesc));
     if (EICTypeTable == ttLibre)
       strcpy( tblDesc.szTblName, TableName.c_str());
     else
      strcpy( tblDesc.szTblName, propsTbl.szName);      ASSERT_VALID_STRING( tblDesc.szTblName);
     strcpy( tblDesc.szTblType, propsTbl.szTableType); ASSERT_VALID_STRING( tblDesc.szTblType);

     // => Paramètre de changement de niveau de la table
     memset( &optParams, 0, sizeof( optParams));
     ASSERT( optParams.iOffset == 0);
     optParams.iLen = sizeof( szLevel);
     strcpy( optParams.szName, szCFGDRVLEVEL); ASSERT_VALID_STRING( optParams.szName);

     // => Lien pour la restructuration
     tblDesc.iOptParams    = 1;
     tblDesc.pfldOptParams = &optParams;
     tblDesc.pOptData      = szLevel;
     // MAJ du niveau de la table //
     tagTmp = TAG( 79);
     Dbtables::Check( Bde::DbiDoRestructure( hDb, 1, &tblDesc, NULL, NULL, NULL, false));
   }
 } catch( Exception& e) {
   Close();
   if( bExclusiveOk)
     Exclusive = bIsExclusive;

   throw EUser( tagTmp, e);
 }
 } catch( Exception& e) {
   throw EUser( tag, e);
 }
} /* CTable::UpgradeLevel() */


void __fastcall CTable::CreateTable( bool bOverWrite,
                                     int nFields          , FLDDesc  *pfldDesc,char *szLangageTable,
                                     int nIndexs /* = 0 */, IDXDesc  *pidxDesc /* = NULL */,
                                     int nChecks /* = 0 */, VCHKDesc *pchkDesc /* = NULL */)
{
  ASSERT( !TableName.IsEmpty());
  ASSERT( !nFields || pfldDesc);
  ASSERT( !nChecks || pchkDesc);
  ASSERT( !nIndexs || pidxDesc);

  TDatabase *pDatabase = NULL;

/*  for( int i=Session->DatabaseCount-1; i>=0; i--)
    if( Session->Databases[i]->DatabaseName.UpperCase()==DatabaseName.UpperCase())
      pDatabase = Session->Databases[i];*/

  if(( pDatabase = DBSession->FindDatabase( DatabaseName))==NULL)
       pDatabase = DBSession->OpenDatabase( DatabaseName);

  ASSERT( pDatabase); pDatabase->Connected = true;
  ASSERT( pDatabase->Handle);

  /*if( !bOverWrite && FileExists( pDatabase->Directory + TableName + ( TableName.UpperCase().Pos( ".DB") ? "" : ".db")))
    return;*/

  if( FileExists( pDatabase->Directory + TableName + ( TableName.UpperCase().Pos( ".DB") ? "" : ".db"))) {
    if( bOverWrite) EmptyTable();
    return;
  }

  CRTblDesc tblDesc; ::ZeroMemory( &tblDesc, sizeof( CRTblDesc));

  strcpy( tblDesc.szTblName, TableName.c_str());
  strcpy( tblDesc.szTblType, szPARADOX        );
  if (szLangageTable)
  {
    FLDDesc OptFLDDesc;
    AnsiString strData = szLangageTable;
    memset( &OptFLDDesc, 0, sizeof( OptFLDDesc));
    strcpy(OptFLDDesc.szName,szCFGDRVLANGDRIVER );
    ASSERT(OptFLDDesc.iOffset ==0);
    OptFLDDesc.iLen =sizeof(szLangageTable);
    tblDesc.iOptParams = 1;
    tblDesc.pfldOptParams = &OptFLDDesc;
    tblDesc.pOptData = szLangageTable;
  }

  tblDesc.iFldCount     = ( unsigned short)nFields; tblDesc.pfldDesc  = pfldDesc;
  tblDesc.iIdxCount     = ( unsigned short)nIndexs; tblDesc.pidxDesc  = pidxDesc;
  tblDesc.iValChkCount  = ( unsigned short)nChecks; tblDesc.pvchkDesc = pchkDesc;

  ::Check( ::DbiCreateTable( pDatabase->Handle, bOverWrite, tblDesc));
}
void __fastcall CTable::CreateTable( bool bOverWrite,
                                     int nFields          , FLDDesc  *pfldDesc,
                                     int nIndexs /* = 0 */, IDXDesc  *pidxDesc /* = NULL */,
                                     int nChecks /* = 0 */, VCHKDesc *pchkDesc /* = NULL */)
{
  CreateTable(bOverWrite, nFields, pfldDesc,NULL, nIndexs,pidxDesc , nChecks,pchkDesc);
}

void __fastcall CTable::CreateTable()
{
TTable::CreateTable();
}
//********************************************//
// Opérations sur une table ouverte ou fermée //
//********************************************//
/******************************************************************************\
************* CTable::PushFilter() *********************************************
********************************************************************************
*                                                                              *
*    Cette procédure permet de définir un nouveau filtre sur la table en       *
* sauvegardant le filtre actuel dans la pile des filtres de la table.          *
*                                                                              *
* Paramètres :                                                                 *
*   fidPushed    : Retourne un ID du filtre empilé sur la pile (DOIT VALOIR    *
*                  FID_NUL en entrée pour que la gestion soit optimale)        *
*   strNewFilter : Nouvelle expression de filtre                               *
*   bDoFilter    : TRUE pour activer le nouveau filtre; FALSE pour désactiver  *
*                  le filtrage (mais positionner quand même le nouveau filtre).*
\******************************************************************************/
void __fastcall CTable::PushFilter( FilterID&         fidPushed,
                                    const AnsiString& strNewFilter,
                                    bool              bDoFilter    /* = true*/)
{
 ASSERT( fidPushed == FID_NUL);

  PushFilter( fidPushed);
// m_pvFiltersStack = new CFilterStack( fidPushed, Filter, Filtered, (CFilterStack*)m_pvFiltersStack);
 // Cette pose est optimisée de façon à éviter un filtrage inutile
 if( bDoFilter)
 { // Application d'un filtre
   // => On pose d'abord le nouveau filtre PUIS on active le filtrage
   Filter   = strNewFilter;
   Filtered = true;
 }
 else
 { // Désactivation du filtrage
   // => On désactive le filtrage PUIS on assigne le nouveau filtre
   Filtered = false;
   Filter   = strNewFilter;
 }
} /* CTable::PushFilter() */


//********************************************//
// Opérations sur une table ouverte ou fermée //
//********************************************//
/******************************************************************************\
************* CTable::PushFilter() *********************************************
********************************************************************************
*                                                                              *
*    Cette procédure permet de définir un nouveau filtre sur la table en       *
* sauvegardant le filtre actuel dans la pile des filtres de la table.          *
*                                                                              *
* Paramètres :                                                                 *
*   fidPushed    : Retourne un ID du filtre empilé sur la pile (DOIT VALOIR    *
*                  FID_NUL en entrée pour que la gestion soit optimale)        *
\******************************************************************************/
void __fastcall CTable::PushFilter( FilterID& fidPushed)
{
 ASSERT( fidPushed == FID_NUL);

 m_pvFiltersStack = new CFilterStack( fidPushed, Filter, Filtered, (CFilterStack*)m_pvFiltersStack);

} /* CTable::PushFilter() */


/******************************************************************************\
************* CTable::PopFilter() **********************************************
********************************************************************************
*                                                                              *
*    Cette procédure permet de restaurer sur la table un filtre identifié par  *
* son ID (tous les filtres mis sur la pile après celui indiqué et celui à      *
* dépilé sont également supprimés de la pile).                                 *
*                                                                              *
* Paramètres :                                                                 *
*   fidToPop : ID du filtre à dépiler (il est retourné à zéro pour pouvoir     *
*              être utilisé par la suite).                                     *
*                                                                              *
* Note : Si l'ID du filtre ne correspond à aucun filtre de la pile, une        *
*        exception est levée mais la pile des filtres n'est pas vidée.         *
\******************************************************************************/
void __fastcall CTable::PopFilter( FilterID& fidToPop)
{
 CFilterStack* pfsToPop;  // Filtre à appliquer sur la table (celui du haut de la pile)
 CFilterStack* pfsNewTop; // Nouveau haut de pile après dépilage
 for( pfsToPop = (CFilterStack*)m_pvFiltersStack; pfsToPop; pfsToPop = pfsToPop->Next)
   if( pfsToPop->ID == fidToPop)
     break;
 if( !pfsToPop)
 { // Erreur: La pile des filtres est vide ou bien le filtre n'a pas été trouvé
   if( fidToPop != FID_NUL)
   { // Le filtre a pourtant - a priori - été poussé
     // (cet ID est très probablement celui du filtre d'une autre table ou bien l'appelant
     // tente de poper le filtre - probablement dans un gestionnaire - d'exception avant
     // d'avoir pu le pusher et SURTOUT, sans l'avoir initialisé à zéro !!!)
     // => On lève une exception
     #ifdef _DEBUG
      if( m_pvFiltersStack)
        throw EInvalidOperation( "La pile des filtres de la table " + TableName + " est vide\n"
                                      "(Nom du composant : \"" + EICComponentName( this) + "\")");
      else
        throw EInvalidOperation( "Filtre non trouvé dans la pile des filtres de la table \"" + TableName + "\"\n"
                                      "(Nom du composant : \"" + EICComponentName( this) + "\")");
     #else
      if( m_pvFiltersStack)
        throw EInvalidOperation( "La pile des filtres de la table \"" + TableName + "\" est vide");
      else
        throw EInvalidOperation( "Filtre non trouvé dans la pile des filtres de la table \"" + TableName + "\"");
     #endif
   }
   else
   { // Le filtre n'a pas été pushé (on est probablement dans un gestionnaire d'exception)
     // => On retourne sans rien faire
   }
 }
 // On va dépiler le filtre (de façon optimisée)
 // => Pose du filtre à dépiler
 if( pfsToPop->Filtered)
 { // On dépile un filtre qui doit s'activer
   // => On pose d'abord le filtre PUIS on active le filtrage (si le filtrage n'est pas déjà activé)
   Filter   = pfsToPop->Filter;
   Filtered = true;
 }
 else
 { // On dépile un filtre inactif
   // => On désactive d'abord le filtrage PUIS on restore le filtre
   Filtered = false;
   Filter   = pfsToPop->Filter;
 }
 pfsNewTop = pfsToPop->Next;
 do
 {
   m_pvFiltersStack = pfsToPop->Next;
   delete pfsToPop;
 } while( ( pfsToPop = (CFilterStack*)m_pvFiltersStack) != pfsNewTop);
 fidToPop = FID_NUL;
} /* CTable::PopFilter() */         

/******************************************************************************\
************* CTable::IsEmpty() ************************************************
********************************************************************************
*                                                                              *
*   Cette fonction permet de corriger le bug de la fonction de Borland.        *
*                                                                              *
\******************************************************************************/
/*
bool __fastcall CTable::IsEmpty()
{
  if (!Active) return TTable::IsEmpty();

  int nRecCount;
  Dbtables::Check( Bde::DbiGetExactRecordCount(Handle, nRecCount));
  return (nRecCount==0);
}
*/
//---------------------------------------------------------------------------
void __fastcall CTable::LocateNearest( const AnsiString &strFieldName, const AnsiString &strText)
{
  TLocateOptions loSearchOptions;
                 loSearchOptions = loSearchOptions << loPartialKey << loCaseInsensitive;

  DisableControls();
  First();
  for( int i=1; i<=strText.Length(); i++)
    Locate( strFieldName, Variant( strText.SubString( 1, i)), loSearchOptions);
  EnableControls();
}
//---------------------------------------------------------------------------
void __fastcall CTable::CopyPrecField( TField *pField, int nStep)
{
  Variant vrValue( GetPrecField( pField, nStep));

  if( vrValue.IsEmpty()) return;
  if( State==dsBrowse) Edit();

  ASSERT( pField);
  pField->AsVariant = vrValue;
}
//---------------------------------------------------------------------------
Variant __fastcall CTable::GetPrecField( TField *pField, int nStep)
{
  ASSERT( nStep<0);

  Variant vrValue;
          vrValue.Clear();

  if( !pField || ( ActiveRecord<(-nStep))) return vrValue;

  char *pBuf = new char[RecordSize];
  Move( ActiveBuffer(), pBuf, RecordSize);
  Move( Buffers[ActiveRecord+nStep], ActiveBuffer(), RecordSize);

  vrValue = pField->AsVariant;

  Move( pBuf, ActiveBuffer(), RecordSize);
  delete []pBuf;

  return vrValue;
}
/*******************************************************************************

Fonction :

Paramètres d'entrée :

Paramètres de sortie :

Valeur retournée :
*******************************************************************************/
bool __fastcall CTable::RenommerChamp(const char*    pcszFldNameSource,
                                      const char*    pcszFldNameCible)
{
hDBIDb    hdb;                 // Handle de la base de données associée à la table
CRTblDesc tblDesc;             // Description BDE de la table
CURProps  propsTbl;            // Propriétés de la table
FLDDesc*  pFldDescs = NULL;    // Descriptions de champs de la table
CROpType* pOps = NULL;         // Opérations à effectuer sur les champs
Word      iFld0Num;            // Indice (en base 0) du champ à changer dans la table
FLDDesc*  pfldDesc = NULL;     // Description BDE du champ à modifier

bool bChanged = false;

try
   {
   if (StrLen(pcszFldNameCible) > 25) //Un nom de champ ne peut faire plus de 25 caractères
      return false;

   GetBDEDescs(hdb, tblDesc, propsTbl, pFldDescs, pOps);
   if(FieldExists(&propsTbl, pFldDescs, pcszFldNameSource, &iFld0Num))
      {
      ASSERT(iFld0Num <= propsTbl.iFields);

      pfldDesc = pFldDescs + iFld0Num;
      strcpy(pfldDesc->szName, pcszFldNameCible);
      *(pOps + iFld0Num) = crMODIFY;

      Dbtables::Check(Bde::DbiDoRestructure(hdb, 1, &tblDesc, NULL, NULL, NULL, false));

      bChanged = true;
      }

   delete[] pOps;      pOps = NULL;
   delete[] pFldDescs;
   }
catch(Exception &e)
   {
   delete[] pOps;      pOps = NULL;
   delete[] pFldDescs;
   throw EUser(TAG(150), e);
   }

return bChanged;
}
//---------------------------------------------------------------------------
enum TYPE_VAR { TV_NONE, TV_FIELD, TV_STRING, TV_DATE, TV_INT };
//---------------------------------------------------------------------------
AnsiString __cdecl MakeFilter( const char *szExpression, const char *szVars, ...)
{
  AnsiString strVariables( szVars);
  AnsiString strVar, strExpresFinal;
  TYPE_VAR   eTypeVar;
  TStrings  *plstVars;
  int        nPos;

  plstVars = new TStringList();
  // Supprime tous les espaces pour facilité le traitement
  for( int i=strVariables.Length(); i>=1; i--)
    if( strVariables[i]==' ') strVariables.Delete( i, 1);

  // Recherche toutes les variables
  va_list lstVars;
  va_start( lstVars, szVars);

  while( !strVariables.IsEmpty()) {
    if(( nPos=strVariables.Pos( ";"))==0) {
      strVar       = strVariables;
      strVariables = EmptyStr;
    } else {
      strVar       = strVariables.SubString( 1, nPos-1);
      strVariables = strVariables.SubString( nPos+1, strVariables.Length());
    }

    // Recherche le type des variables
         if( strVar.Pos( "FIELD:" )==1) eTypeVar = TV_FIELD;
    else if( strVar.Pos( "STRING:")==1) eTypeVar = TV_STRING;
    else if( strVar.Pos( "DATE:"  )==1) eTypeVar = TV_DATE;
    else if( strVar.Pos( "INT:"   )==1) eTypeVar = TV_INT;
    else                                eTypeVar = TV_NONE;

    if( eTypeVar==TV_NONE) {
      ShowMessage( "Type variable inconnu ou non défini !");
      return EmptyStr;
    }

    strVar = strVar.SubString( strVar.Pos( ":")+1, strVar.Length());

    // Vérifie que les variables sont bien en minuscule
    for( int i=1; i<=strVar.Length(); i++) {
      if( !islower( strVar[i]) && ( strVar[i]!=',')) {
        ShowMessage( "Nom de variables incorrects !");
        return EmptyStr;
      }
    }

    // Création de la liste contenant toutes les variables
    AnsiString strV, strValue;
    while( !strVar.IsEmpty()) {
      if(( nPos=strVar.Pos( ","))==0) {
        strV   = strVar;
        strVar = EmptyStr;
      } else {
        strV   = strVar.SubString( 1, nPos-1);
        strVar = strVar.SubString( nPos+1, strVar.Length());
      }
      // Extrait la valeur de la variable dans la liste des paramètres
      switch( eTypeVar) {
        case TV_FIELD  : strValue = (( TField*)va_arg( lstVars, TField*))->FieldName;
                         break;
        case TV_STRING : strValue = "'" + AnsiString(( char*)va_arg( lstVars, char*)) + "'";
                         break;
        case TV_DATE   : strValue = "'" + TDateTime(( int)va_arg( lstVars, int)).DateString() + "'";
                         break;
        case TV_INT    : strValue = IntToStr(( int)va_arg( lstVars, int));
                         break;
        default        : ASSERT( false);
                         strValue = EmptyStr;
                         break;
      }
      // Ajout dans la liste des variables
      plstVars->Values[strV] = strValue;
    }
  }
  va_end( lstVars);

  // Traitement de l'expression
  char *szExpres = ( char*)szExpression;
  for( ; *szExpres!='\0'; szExpres++) {
    if( islower( *szExpres)) {
      // On a affaire à une variable
      strVar = AnsiString( *szExpres);
      for( szExpres++; islower( *szExpres) && ( *szExpres!='\0'); szExpres++)
        strVar = strVar + AnsiString( *szExpres);
      strExpresFinal = strExpresFinal + plstVars->Values[strVar];
      szExpres--;
    } else
      strExpresFinal = strExpresFinal + AnsiString( *szExpres);
  }

  delete plstVars;
  return strExpresFinal;
}
//---------------------------------------------------------------------------
// ValidCtrCheck est utilisé pour être sûr que les composants créés ne
// contiennent aucune fonction virtuelle pure.
//
static inline void ValidCtrCheck(CTable *)
{
    new CTable(NULL);
}
//---------------------------------------------------------------------------
namespace Ctable
{
    void __fastcall PACKAGE Register()
    {
        TComponentClass classes[1] = {__classid(CTable)};
        RegisterComponents("EIC CtrlDB", classes, 0);
    }
}




